# !/usr/bin/python
# _*_ coding: utf-8 _*_
# @Time : 2021/5/25 4:09 下午
# @Author : liujun
# @File : server.py
# @desc : 服务配置

import traceback

from fastapi import FastAPI, Request, Response
from starlette.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError, ValidationError

from api.v1_router import api_router
from core.config import settings
from common import logger, my_exc
from common.my_schedule import schedule
from common.my_redis import redis_client
from schemas.response import result, result_code


def create_app() -> FastAPI:
    """
    生成FatAPI对象
    :return:
    """
    app = FastAPI(
        title=settings.TITLE,
        openapi_url=f'{settings.API_V1_STR}/openapi.json'
    )
    # 跨域设置
    register_cors(app)
    # 注册路由
    register_router(app)
    # 注册捕获全局异常
    register_exception(app)
    # 请求拦截
    register_req_aop(app)
    # 取消挂载在 request对象上面的操作，感觉特别麻烦，直接使用全局的
    register_init(app)
    # 注册静态文件
    register_static_file(app)
    return app


def register_cors(app: FastAPI) -> None:
    """
    跨域设置
    :param app:
    :return:
    """
    if settings.BACKEND_CORS_ORIGINS:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )


def register_router(app: FastAPI) -> None:
    """
    注册路由
    :param app:
    :return:
    """
    app.include_router(api_router, prefix=settings.API_V1_STR)


def register_exception(app: FastAPI) -> None:
    """
    全局异常捕获
    :param app:
    :return:
    """
    @app.exception_handler(my_exc.TokenExpired)
    async def user_not_found_exception_handler(request: Request, exc: my_exc.TokenExpired):
        """
        token过期
        :param request:
        :param exc:
        :return:
        """
        logger.error(
            f"token未知用户\nURL:{request.method}{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}")
        return result.failure(result_code=exc.result_code)

    @app.exception_handler(my_exc.TokenAuthError)
    async def user_token_exception_handler(request: Request, exc: my_exc.TokenAuthError):
        """
        用户token异常
        :param request:
        :param exc:
        :return:
        """
        logger.error(
            f"用户认证异常\nURL:{request.method}{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}")
        return result.failure(result_code=exc.result_code)

    @app.exception_handler(my_exc.AuthenticationError)
    async def user_not_found_exception_handler(request: Request, exc: my_exc.AuthenticationError):
        """
        用户权限不足
        :param request:
        :param exc:
        :return:
        """
        logger.error(f"用户权限不足 \nURL:{request.method}{request.url}")
        return result.failure(result_code=exc.result_code)

    @app.exception_handler(ValidationError)
    async def inner_validation_exception_handler(request: Request, exc: ValidationError):
        """
        内部参数验证异常
        :param request:
        :param exc:
        :return:
        """
        logger.error(
            f"内部参数验证错误\nURL:{request.method}{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}")
        return result.failure(result_code=exc.result_code)

    @app.exception_handler(RequestValidationError)
    async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
        """
        请求参数验证异常
        :param request:
        :param exc:
        :return:
        """
        logger.error(
            f"请求参数格式错误\nURL:{request.method}{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}")
        return result.failure(result_code=exc.result_code)

    # 捕获全部异常
    @app.exception_handler(Exception)
    async def all_exception_handler(request: Request):
        """
        全局所有异常
        :param request:
        :param exc:
        :return:
        """
        logger.error(
            f"全局异常\n{request.method}URL:{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}")
        return result.failure(result_code=result_code.ResultCode.SYSTEM_INNER_ERROR)


def register_req_aop(app: FastAPI) -> None:
    """
    请求响应拦截器
    https://fastapi.tiangolo.com/tutorial/middleware/
    :param app:
    :return:
    """

    @app.middleware("http")
    async def logger_request(request: Request, call_next) -> Response:
        logger.info(f"访问记录:{request.method} url:{request.url}\nheaders:{request.headers}\nIP:{request.client.host}")
        response = await call_next(request)
        return response


def register_init(app: FastAPI) -> None:
    """
    初始化连接
    :param app:
    :return:
    """
    @app.on_event("startup")
    async def init_connect():
        # 连接redis
        redis_client.init_redis_connect()

        # 初始化 scheduler
        schedule.init_scheduler()

    @app.on_event('shutdown')
    async def shutdown_connect():
        """
        关闭
        :return:
        """
        schedule.shutdown()


def register_static_file(app: FastAPI) -> None:
    """
    静态文件交互开发模式使用
    生产使用 nginx 静态资源服务
    这里是开发是方便本地
    :param app:
    :return:
    """
    import os
    from fastapi.staticfiles import StaticFiles
    if not os.path.exists("./static"):
        os.mkdir("./static")
    app.mount("/static", StaticFiles(directory="static"), name="static")

